#include <QFile>
#include <QTextStream>
#include <QMessageBox>
// STL
#include <sstream>
#include <string>
#include <iterator>
#include <algorithm>
#include <complex>
#include <cassert>
// boost
#include <boost/lambda/lambda.hpp>

#include "mv.h"
#include <models/model_mesh.h>

namespace formats { namespace mv {

using namespace models;

//
// Frame_reader
//

class Frame_reader : public Mesh::Frame_reader {
public:
  Frame_reader(const QString &file_name, QMap<QString, QString> & _PropertyMap);
  virtual Mesh::Frame::SP read();

private:
  bool parse_comment(const QString &Line);
  
private:
  QFile       file_;
  QTextStream stream_;
  Mesh::Geometry::SP                p_geometry_;            // mv geometry
  QVector<Mesh::Scalar_field::SP>   scalar_fields_;         // mv vertex scalar fields
  QVector<Mesh::Scalar_field::SP>   element_scalar_fields_; // mv element scalar fields
  QMap<QString, QString>          & m_PropertyMap;
};

Frame_reader::Frame_reader(const QString &file_name, QMap<QString, QString> & _PropertyMap)
  : file_(file_name),
    stream_(&file_),
    m_PropertyMap(_PropertyMap)
{
  if (!file_.open(QIODevice::ReadOnly | QIODevice::Text))
    throw QObject::tr("unable to open mv file: ") + file_name;

  QString Line;
  IMesh::t_container_vertex vertices;
  QMap<IMesh::element_type, IMesh::t_container_element> element_map;

  while (parse_comment(Line = stream_.readLine()));

  { // read vertices
    QTextStream LineStream(&Line);

    quint32 node_qty, dim_qty, field_qty, node_number;
    qreal x, y, z;

    LineStream >> node_qty;
    LineStream >> dim_qty;
    LineStream >> field_qty;
    QVector<QString> field_names(field_qty);
    QVector<QVector<Mesh::Scalar_field::param_t>> field_values(field_qty);
    for (int i = 0; i < field_qty; i++)
    {
      LineStream >> field_names[i];
      field_values[i].resize(node_qty);
    }

    vertices.resize(node_qty);
    for (quint32 entry = 0; entry < node_qty; ++entry)
    {
      QString Line = stream_.readLine().replace(',', '.');
      QTextStream LineStream(&Line);

      LineStream >> node_number >> x >> y >> z;
      vertices[node_number - 1] = IMesh::vector(x, y, z);
      for (int i = 0; i < field_qty; i++)
        LineStream >> field_values[i][entry];
    }
    
    scalar_fields_.resize(field_qty);
    for (int i = 0; i < field_qty; i++)
      scalar_fields_[i].reset(new Mesh::Scalar_field(field_names[i], field_values[i]));
  } // read vertices

  while (parse_comment(Line = stream_.readLine()));
  
  { // read elements
    quint32 element_qty, approximationOrder, field_qty, node_number;
    IMesh::t_container_element &nodes = element_map[IMesh::element_triangle];

    QTextStream LineStream(&Line);

    LineStream >> element_qty;
    LineStream >> approximationOrder;
    LineStream >> field_qty;
    QVector<QString> field_names(field_qty);
    QVector<QVector<Mesh::Scalar_field::param_t>> field_values(field_qty);
    for (int i = 0; i < field_qty; i++)
    {
      LineStream >> field_names[i];
      field_values[i].resize(element_qty * 3);
    }

    nodes.resize(element_qty * 3);
    
    const qint32 step = approximationOrder / 3;

    for (quint32 elementIndex = 0; elementIndex < element_qty; ++elementIndex)
    {
      Line = stream_.readLine().replace(',', '.');
      QTextStream LineStream(&Line);

      const quint32 baseIndex = elementIndex * 3;
      LineStream >> node_number;
      
      for (qint32 nodeIndex = 0; nodeIndex < approximationOrder; nodeIndex++)
      {
        qint32 index = 0;
        
        LineStream >> index;
        
        if (!(nodeIndex % step))
          nodes[elementIndex * 3 + nodeIndex / step] = index - 1;
      }
        
      for (int i = 0; i < field_qty; i++)
      {
        LineStream >> field_values[i][elementIndex * 3];
        field_values[i][elementIndex * 3 + 1] = field_values[i][elementIndex * 3];
        field_values[i][elementIndex * 3 + 2] = field_values[i][elementIndex * 3];
      }
    }

    element_scalar_fields_.resize(field_qty);
    for (int i = 0; i < field_qty; i++)
      element_scalar_fields_[i].reset(new Mesh::Scalar_field(field_names[i], field_values[i]));
  } // read elements
  p_geometry_.reset(new Mesh::Geometry(vertices, element_map));
}

Mesh::Frame::SP Frame_reader::read()
{
  Mesh::Frame::SP p_frame;

  if (p_geometry_)
  {
    p_frame.reset(new Mesh::Frame(p_geometry_, scalar_fields_));
    p_frame->set_element_field(IMesh::element_triangle, element_scalar_fields_);
    
    p_geometry_.reset();
  }
  return p_frame;
}

bool Frame_reader::parse_comment(const QString & Line)
{
  if (Line[0] != '#')
    return false;

  const int EqualIndex = Line.indexOf('=');
  if (EqualIndex > 0)
  {
    const QString Key = Line.mid(1, EqualIndex - 1).trimmed();
    const QString Val = Line.right(Line.length() - 1 - EqualIndex).trimmed();

    m_PropertyMap[Key] = Val;
  }

  return true;
}

//
// mv::Model_creator
//

bool Format::IsStorable() const
{
  return false;
}

bool Format::IsRestorable() const
{
  return true;
}

void Format::Store(QObject *, const QString &) const
{
  assert(!"mv storing not supported");
}

QObject *Format::Restore(const QString &file_name) const
{
  using namespace std;
  using namespace boost;
  using namespace boost::lambda;

  Mesh *p_model = 0;
  try {
    QMap<QString, QString> PropertyMap;
    Mesh::Frame_reader::SP p_frame_reader(new Frame_reader(file_name, PropertyMap));

    p_model = new Mesh(p_frame_reader);
    for (QMap<QString, QString>::iterator it = PropertyMap.begin(); it != PropertyMap.end(); ++it)
      p_model->setProperty(it.key().toAscii(), it.value());

    p_model->set_current_frame(0);
  }
  catch (QString &msg)
  {
    QMessageBox::critical(0, QObject::tr("MeshViewer format"), msg);
  }
  return p_model;
}

QString Format::GetDisplayName() const
{
  return QObject::tr("MeshViewer format");
}

QString Format::GetFilter() const
{
  return QObject::tr("MeshViewer format (*.mv)");
}

} } // namespace formats { namespace mv {